#pragma once

#include <exception>
#include <iostream>
#include <string>
#include <utility>

#include "txdnn_internal.h"

// #define LOG_TAG "macros.Exception"

namespace txdnn {

struct Exception : public std::exception {
  Exception(const std::string& msg = "") : msg_(msg), status_(TXDNN_STATUS_UNKNOWN) {}
  Exception(const std::string& msg, txdnnStatus_t s) : msg_(msg), status_(s) {}

  Exception setContext(const std::string& file, int line) {
    msg_ = file + ": " + std::to_string(line) + ": " + msg_;
    return *this;
  }

  const char* what() const noexcept override { return msg_.c_str(); }

  std::string msg_;
  txdnnStatus_t status_;
};


template<class... Params>
void TxdnnThrow(const std::string& file, int line, Params&&... params) {
  throw txdnn::Exception(std::forward<Params>(params)...).setContext(file, line);
}

#define TXDNN_THROW(...)                                \
  do {                                                  \
    txdnn::TxdnnThrow(__FILE__, __LINE__, __VA_ARGS__); \
  } while(false)


// Exception caught
template <class F>
txdnnStatus_t TryCatch(F f, bool output = true) {
  try {
    f();
  } catch (const std::exception& e) {
    if (output) {
      // TXLOG_ERROR(e.what());
      return TXDNN_STATUS_UNKNOWN;
    }
  } catch (...) {
    return TXDNN_STATUS_UNKNOWN;
  }
  return TXDNN_STATUS_SUCCESS;
}


// TODO debug build should leave the exception uncaught
template <class F>
txdnnStatus_t TxdnnTry(F f, bool output = true) {
  try {
    f();
  }
  catch (const Exception& e) {
    if (output) {
      std::cerr << "TxDNN Error: " << e.what() << std::endl;
    }
    return e.status_;
  }
  catch (const std::exception& e) {
    if (output) {
      std::cerr << "TxDNN Error: " << e.what() << std::endl;
    }
    return TXDNN_STATUS_BAD_PARAM;
  }
  catch (...) {
    return TXDNN_STATUS_BAD_PARAM;
  }
  return TXDNN_STATUS_SUCCESS;
}


template <class T>
auto define_obj(T&& x, txdnnStatus_t err = TXDNN_STATUS_BAD_PARAM) -> decltype((x == nullptr), get_object(*x)) {
  if (x == nullptr) {
    TXDNN_THROW("input must not be a nullptr");
  }
  return get_object(*x);
}

} // namespace txdnn